﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using HAPI;
using HuginApiAddonsCS.Extensions;

namespace HuginApiAddonsCS.Simulator
{
    public class Simulator
    {
        private List<SimulationResult> _results = new List<SimulationResult>();
        private int _simulationCount = 0;

        /// <summary>
        /// Simulated a DID model, sets up files and
        /// threads
        /// </summary>
        /// <param name="statePrefix">state prefix</param>
        /// <param name="obsPrefix">observation prefix</param>
        /// <param name="actPrefix">action prefix</param>
        /// <param name="utilPrefix">utility prefix</param>
        /// <param name="inputDomains">input domains</param>
        /// <param name="resultsFile">output results file</param>
        /// <param name="numSimulations">number of simulations to execute</param>
        /// <param name="numThreads">number of threads to use</param>
        public void SimulateDid(string statePrefix, string obsPrefix, string actPrefix, 
            string utilPrefix, List<string> inputDomains, string resultsFile, int numSimulations, int numThreads)
        {
            //Divide Simulations count into threads
            int[] simulationsPerThread = new int[numThreads];
            int simThread = numSimulations/numThreads;
            int remainder = numSimulations%numThreads;
            for (int i = 0; i < numThreads; i++)
            {
                simulationsPerThread[i] = simThread;
                if (remainder > 0)
                {
                    simulationsPerThread[i]++;
                    remainder --;
                }
            }

            //Load Domain Files
            Console.WriteLine("Loading Files");
            List<Domain> domains = new List<Domain>();
            foreach (string inputDomain in inputDomains)
            {
                ParseListener pl = new DefaultClassParseListener();
                Domain domain = new Domain(inputDomain, pl);
                domains.Add(domain);
            }
            
            //Setup threads
            List<Thread> threads = new List<Thread>();
            for (int i = 0; i < simulationsPerThread.Count(); i++)
            {
                //copy domain list
                List<Domain> tempDomains = new List<Domain>();
                foreach (Domain domain in domains)
                {
                    tempDomains.Add((Domain)domain.Clone());
                }

                int simsPer = simulationsPerThread[i];
                Thread t = new Thread(delegate()
                {
                    SimulateDid(statePrefix, obsPrefix, actPrefix, utilPrefix, tempDomains, simsPer);
                });
                threads.Add(t);
            }
            //Execute all threads
            Console.WriteLine("Executing Simulations");
            foreach (Thread thread in threads)
            {
                thread.Start();
            }

            //Wait for thread completion
            bool complete = false;
            while (!complete)
            {
                complete = true;
                foreach (Thread thread in threads)
                {
                    if (thread.IsAlive)
                    {
                        complete = false;
                    }
                }
                System.Threading.Thread.Sleep(100);

                Console.Write("\rProgress {0}%   ", (((float)_simulationCount / (float)numSimulations) * 100.0f));
            }

            Console.WriteLine();

            //write results to file
            WriteResultsToFile(resultsFile);
        }

        /// <summary>
        /// Used as part of a thread to loop and run the number of simulations delegated to the thread
        /// </summary>
        /// <param name="statePrefix">state prefix</param>
        /// <param name="obsPrefix">observation prefix</param>
        /// <param name="actPrefix">action prefix</param>
        /// <param name="utilPrefix">utility prefix</param>
        /// <param name="inputDomains">input domains</param>
        /// <param name="numSimulations">number of simulations</param>
        public void SimulateDid(string statePrefix, string obsPrefix, string actPrefix, string utilPrefix,
            List<Domain> inputDomains, int numSimulations)
        {
            foreach (Domain inputDomain in inputDomains)
            {
                inputDomain.Compile();
            }

            Random random = new Random();

            for (int i = 0; i < numSimulations; i++)
            {
                //Choose a random domain and call simulate on it and add its results to list
                int dChoice = random.Next(inputDomains.Count);
                List<SimulationResult> tempResults = inputDomains[dChoice].GetSimulationResultDid(actPrefix, obsPrefix,
                    statePrefix, utilPrefix, "Mod" + dChoice);
                lock (_results)
                {
                    _simulationCount ++;
                    foreach (SimulationResult result in tempResults)
                    {
                        _results.Add(result);
                    }
                }
            }
        }

        /// <summary>
        /// Loads files and sets up each thread for execution
        /// </summary>
        /// <param name="statePrefix">State Prefix</param>
        /// <param name="obsIPrefix">agent i observation</param>
        /// <param name="actIPrefix">agent i action</param>
        /// <param name="utilPrefix">utility</param>
        /// <param name="obsJPrefix">agent j observation</param>
        /// <param name="actJPrefix">agent j action</param>
        /// <param name="inputIdid">input i-did</param>
        /// <param name="inputDomains">input did domains</param>
        /// <param name="resultsFile">output results csv file</param>
        /// <param name="numSimulations">numbe of simulations to execute</param>
        /// <param name="numThreads">number of threads</param>
        public void SimulateIdid(string statePrefix, string obsIPrefix, string actIPrefix, string utilPrefix,
            string obsJPrefix, string actJPrefix, string inputIdid, List<string> inputDomains, string resultsFile, 
            int numSimulations, int numThreads)
        {
            //Divide Simulations count into threads
            int[] simulationsPerThread = new int[numThreads];
            int simThread = numSimulations/numThreads;
            int remainder = numSimulations%numThreads;
            for (int i = 0; i < numThreads; i++)
            {
                simulationsPerThread[i] = simThread;
                if (remainder > 0)
                {
                    simulationsPerThread[i]++;
                    remainder --;
                }
            }

            //Load Domain Files
            Console.WriteLine("Loading Files");
            ParseListener pli = new DefaultClassParseListener();
            Domain iDomain = new Domain(inputIdid, pli);

            List<Domain> jDomains = new List<Domain>();
            foreach (string inputDomain in inputDomains)
            {
                ParseListener pl = new DefaultClassParseListener();
                Domain jDomain = new Domain(inputDomain, pl);
                jDomains.Add(jDomain);
            }

            //Setup Threads
            Random random = new Random();
            List<Thread> threads = new List<Thread>();
            for (int i = 0; i < simulationsPerThread.Count(); i++)
            {
                //copy domain list
                List<Domain> tempJDomains = new List<Domain>();
                foreach (Domain domain in jDomains)
                {
                    tempJDomains.Add((Domain)domain.Clone());
                }
                Domain tempIDomain = (Domain)iDomain.Clone();

                int simsPer = simulationsPerThread[i];
                Thread t = new Thread(delegate()
                {
                    SimulateIdid(statePrefix, obsIPrefix, actIPrefix, utilPrefix, obsJPrefix, actJPrefix, tempIDomain,
                        tempJDomains, simsPer, random);
                });
                threads.Add(t);
            }

            //Execute all threads
            Console.WriteLine("Executing Simulations");
            foreach (Thread thread in threads)
            {
                thread.Start();
            }

            //Wait for thread completion
            bool complete = false;
            while (!complete)
            {
                complete = true;
                foreach (Thread thread in threads)
                {
                    if (thread.IsAlive)
                    {
                        complete = false;
                    }
                }
                System.Threading.Thread.Sleep(100);

                Console.Write("\rProgress {0}%   ", (((float)_simulationCount / (float)numSimulations) * 100.0f));
            }

            Console.WriteLine();

            //write results to file
            WriteResultsToFile(resultsFile);

        }

        /// <summary>
        /// Executed by each thread, loops until it delegated number of simulations have completed
        /// </summary>
        /// <param name="statePrefix">state prefix</param>
        /// <param name="obsIPrefix">agent i observation prefix</param>
        /// <param name="actIPrefix">agent i action prefix</param>
        /// <param name="utilPrefix">utility prefix</param>
        /// <param name="obsJPrefix">agent j observation prefix</param>
        /// <param name="actJPrefix">agent j action prefix</param>
        /// <param name="inputIdid">input i-did domain</param>
        /// <param name="inputDomains">input did domains</param>
        /// <param name="numSimulations">number of simulations for thread to execute</param>
        public void SimulateIdid(string statePrefix, string obsIPrefix, string actIPrefix, string utilPrefix,
            string obsJPrefix, string actJPrefix, Domain inputIdid, List<Domain> inputDomains, int numSimulations, Random random)
        {
            foreach (Domain inputDomain in inputDomains)
            {
                inputDomain.Compile();
            }
            inputIdid.Compile();

            //Random random = new Random();

            for (int i = 0; i < numSimulations; i++)
            {
                //Choose a random domain and call simulate on it and add its results to list
                int dChoice = random.Next(inputDomains.Count);
                List<SimulationResult> tempResults = inputIdid.GetSimulationResultIdid(inputDomains[dChoice], actJPrefix, 
                    obsJPrefix, actIPrefix, obsIPrefix, statePrefix, utilPrefix, "IDID");
                lock (_results)
                {
                    _simulationCount++;
                    foreach (SimulationResult result in tempResults)
                    {
                        _results.Add(result);
                    }
                }
            }
        }

        /// <summary>
        /// Writes all the stored results to a csv file
        /// </summary>
        /// <param name="resultsFile">output file path and name</param>
        public void WriteResultsToFile(string resultsFile)
        {
            Console.WriteLine("Writing Results to " + resultsFile);

            StreamWriter file2 = new StreamWriter(resultsFile);
            file2.WriteLine("Model,TimeStep,State,Observation,Action,Utility,");
            
            foreach (SimulationResult result in _results)
            {
                file2.WriteLine(result.ToString());
            }

            file2.Close();
        }
    }
}
